﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using MoonLight.LibCommon;

namespace MoonLight.LibSocket
{
    public class ReceiveObject
    {
        public ReceiveObject()
        {
            rcv_buf = new byte[buf_size];
            rcv_data = new StringBuilder();
        }
        public Socket work_sock = null;
        public int buf_size = 1024;
        public byte[] rcv_buf;
        public StringBuilder rcv_data;
    }

    public class AsynTcpSocket
    {
        public AsynTcpSocket(Notifier parent)
        {
            m_parent = parent;
        }

        ~AsynTcpSocket()
        {

        }

        #region handle socket
        public int CreateSocket()
        {
            try
            {
                m_sock = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                Logger.WriteLog("create a socket");
            }
            catch (Exception ex) {
                Logger.WriteLog(ex.Message.ToString());
                return -1;
            }
            return 0;
        }

        public int SetTcpSocket(Socket sock)
        {
            if (sock == null ||
                sock.AddressFamily != AddressFamily.InterNetwork ||
                sock.SocketType != SocketType.Stream ||
                sock.ProtocolType != ProtocolType.Tcp)
                return -1;

            m_sock = sock;
            return 0;
        }

        public int Connect(string ip, int port)
        {
            if (m_sock == null)
                return -1;
            try
            {
                m_sock.BeginConnect(IPAddress.Parse(ip), port, new AsyncCallback(ConnectCallBack), m_sock);
                Logger.WriteLog("try to connect to host[" + ip.ToString() + "][" + port.ToString() + "]");
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                return -1;
            }
            return 0;
        }

        public int Listen(string ip, int port, int backlog)
        {
            if (m_sock == null)
                return -1;
            IPEndPoint ip_ep = new IPEndPoint(IPAddress.Parse(ip), port);
            try
            {
                m_sock.Bind(ip_ep);
                m_sock.Listen(backlog);
                while (true)
                {
                    //set the event to nonsignaled state. 
                    m_mre.Reset();

                    Logger.WriteLog("Waiting for a connection");
                    m_sock.BeginAccept(new AsyncCallback(AcceptCallback), m_sock);

                    //wait until a connection is made before continuing.
                    m_mre.WaitOne();
                }
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                return -1;
            }
        }

        public int Send(byte[] bytes)
        {
            if (m_sock == null)
                return -1;
            try
            {
                m_sock.BeginSend(bytes, 0, bytes.Length, 0, new AsyncCallback(SendCallback), m_sock);
                Logger.WriteLog("begin to send");
            }
            catch (SocketException ex)
            {
                m_sock.Close();
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                return -1;
            }
            return 0;
        }

        public int Receive()
        {
            if (m_sock == null)
                return -1;
            ReceiveObject rcv_obj = new ReceiveObject();
            rcv_obj.work_sock = m_sock;
            try
            {
                m_sock.BeginReceive(rcv_obj.rcv_buf, 0, rcv_obj.buf_size, 0, new AsyncCallback(ReadCallBack), rcv_obj);
                Logger.WriteLog("begin to receive");
            }
            catch (SocketException ex)
            {
                m_sock.Close();
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                return -1;
            }
            return 0;
        }

        public int Close()
        {
            if (m_sock != null)
                m_sock.Close();
            MessageInfo msg = new MessageInfo(CommonDefine.MsgType_e.MSG_TCP_CLOSE);
            m_parent.handle_message(msg);
            return 0;
        }
        #endregion

        #region callback
        private void ConnectCallBack(IAsyncResult ar)
        {
            Logger.WriteLog("enter ConnectCallBack");
            Socket handler = (Socket)ar.AsyncState;
            MessageInfo msg = new MessageInfo();
            try
            {
                handler.EndConnect(ar);
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_ERR;
                msg.msg_data = ex.ErrorCode;
                m_parent.handle_message(msg);
                return;
            }

            if (handler.Connected)
            {
                IPEndPoint remote_end_point = (IPEndPoint)(handler.RemoteEndPoint);
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_CONNECT;
                msg.msg_data = remote_end_point.Port.ToString();
            }
            m_parent.handle_message(msg);
        }

        private void AcceptCallback(IAsyncResult ar)
        {
            Logger.WriteLog("enter AcceptCallBack");
            //signal the main thread to continue.
            m_mre.Set();
            Socket listener = (Socket)ar.AsyncState;
            Socket handler = null;
            MessageInfo msg = new MessageInfo();
            try
            {
                handler = listener.EndAccept(ar);
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_ERR;
                msg.msg_data = ex.ErrorCode;
                m_parent.handle_message(msg);
                return;
            }

            if (handler.Connected)
            {
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_ACCEPT;
                msg.msg_data = handler;
            }
            m_parent.handle_message(msg);
        }

        private void ReadCallBack(IAsyncResult ar)
        {
            Logger.WriteLog("enter ReadCallBack");
            string content = string.Empty;
            ReceiveObject rcv_obj = (ReceiveObject)ar.AsyncState;
            Socket work_sock = rcv_obj.work_sock;
            MessageInfo msg = new MessageInfo();
            int readbytes = 0;
            try
            {
                readbytes = work_sock.EndReceive(ar);
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_ERR;
                msg.msg_data = ex.ErrorCode;
                m_parent.handle_message(msg);
                return;
            }
             
            if (readbytes > 0)
            {
                rcv_obj.rcv_data.Append(Encoding.UTF8.GetString(rcv_obj.rcv_buf, 0, readbytes));
                content = rcv_obj.rcv_data.ToString();

                if (work_sock.Available == 0)
                {
                    msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_READ;
                    msg.msg_data = rcv_obj.rcv_data.ToString();
                    m_parent.handle_message(msg);
                }
                else
                {
                    Array.Clear(rcv_obj.rcv_buf, 0, rcv_obj.buf_size);
                    Logger.WriteLog("continue to receive [" + work_sock.Available + "] bytes left");
                    work_sock.BeginReceive(rcv_obj.rcv_buf, 0, rcv_obj.buf_size, 0, new AsyncCallback(ReadCallBack), rcv_obj);
                }
            }
        }

        private void SendCallback(IAsyncResult ar)
        {
            Logger.WriteLog("enter SendCallBack");
            Socket handler = (Socket)ar.AsyncState;
            MessageInfo msg = new MessageInfo();
            int sendbytes = 0;
            try
            {
                sendbytes = handler.EndSend(ar);
            }
            catch (SocketException ex)
            {
                Logger.WriteLog("socket err[" + ex.ErrorCode + "][" + ex.Message + "]");
                msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_ERR;
                msg.msg_data = ex.ErrorCode;
                m_parent.handle_message(msg);
                return;
            }

            Logger.WriteLog("sent [" + sendbytes + "] bytes to client");
            msg.msg_type = CommonDefine.MsgType_e.MSG_TCP_SEND;
            m_parent.handle_message(msg);
        }
        #endregion

        private static ManualResetEvent m_mre = new ManualResetEvent(false);
        private Socket m_sock;
        private Notifier m_parent;
    }
}
